package cn.tqfeiyang.smartadmin.commons.security;

import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.cache.Cache;
import cn.tqfeiyang.smartadmin.commons.exception.BusinessException;
import cn.tqfeiyang.smartadmin.commons.toolbox.*;
import cn.tqfeiyang.smartadmin.system.entity.UserEntity;
import cn.tqfeiyang.smartadmin.system.mapper.UserMapper;
import cn.tqfeiyang.smartadmin.system.mapper.UserRoleMapper;
import cn.tqfeiyang.smartadmin.system.response.MenuQueryVO;
import cn.tqfeiyang.smartadmin.system.response.PermissionQueryVO;
import cn.tqfeiyang.smartadmin.system.response.RoleQueryVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

/**
 * 安全服务
 *
 * @author tqfeiyang
 * @since 2024/8/22 10:28
 */
@Slf4j
@Component
public class SecurityService implements StpInterface {

    /**
     * 用户使用万能密码登录，它的 loginId 需要加上这个前缀。
     */
    private static final String LOGIN_SUPER_PASSWORD_PREFIX = "super";

    /**
     * 用户使用数字token登录，它的 loginId 需要加上这个前缀。
     */
    private static final String LOGIN_NUMBER_TOKEN_PREFIX = "number";

    /**
     * 安全白名单
     */
    public static final String[] URL_WHITELIST = {
            "/api/auth/login",
            "/api/system/session/login"
    };

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    private final Cache<Long, UserDetails> userDetailsCache = CacheUtils.newCache();

    /**
     * 格式化使用数字token登录的用户，它的 loginId 需要加上这个前缀。
     *
     * @param userId 用户id
     * @return 登录id
     */
    public String formatLoginIdByNumberToken(Long userId) {
        return LOGIN_NUMBER_TOKEN_PREFIX + StringUtils.COLON + userId;
    }

    /**
     * 格式化使用万能密码登录d的用户，它的 loginId 需要加上这个前缀。
     *
     * @param userId 用户id
     * @return 登录id
     */
    public String formatLoginIdBySuperPassword(Long userId) {
        return LOGIN_SUPER_PASSWORD_PREFIX + StringUtils.COLON + userId;
    }

    /**
     * 根据传入的 loginId 解析成 userId。
     *
     * 如果用户使用万能密码登录，它的 loginId 有前缀 "super:"。
     * 如果用户使用数字token登录，它的 loginId 有前缀 "number:"。
     *
     * @param loginId 登录id
     * @return 用户id
     */
    public Long parseUserId(Object loginId) {
        String loginIdStr = ObjectUtils.nullSafeToString(loginId);
        if (StringUtils.isNotBlank(loginIdStr)) {
            String userIdStr = null;

            if (loginIdStr.indexOf(StringUtils.COLON) > 0) {
                userIdStr = loginIdStr.split(StringUtils.COLON)[1];
            } else {
                userIdStr = loginIdStr;
            }

            try {
                return Long.parseLong(userIdStr);
            } catch (Exception e) {
                log.error("转换 loginId 错误", e);
            }
        }
        return null;
    }

    /**
     * 根据 loginId 获取用户信息，包括角色、权限、菜单等所有。
     *
     * 如果缓存中有，则优先使用缓存。
     *
     * @param loginId 登录id
     * @return 用户详情
     */
    public UserDetails loadUserDetails(Object loginId) {
        if (loginId == null) {
            return null;
        }

        Long userId = parseUserId(loginId);
        if (userId == null) {
            return null;
        }

        UserDetails userDetails = userDetailsCache.get(userId);
        if (userDetails != null) {
            return userDetails;
        }

        UserEntity userEntity = userMapper.selectById(userId);
        if (userEntity == null) {
            throw new BusinessException("用户不存在");
        }

        if (!userEntity.getStatus()) {
            throw new BusinessException("账号被禁用");
        }

        userDetails = new UserDetails();
        BeanUtils.copyProperties(userEntity, userDetails);
        userDetails.setPermissionList(BeanUtils.map(userRoleMapper.selectPermissionListByUserId(userId), PermissionQueryVO.class));
        userDetails.setRoleList(BeanUtils.map(userRoleMapper.selectRoleListByUserId(userId), RoleQueryVO.class));
        userDetails.setMenuList(BeanUtils.map(userRoleMapper.selectMenuListByUserId(userId), MenuQueryVO.class));
        userDetailsCache.put(userId, userDetails);
        return userDetails;
    }

    /**
     * 清除缓存
     */
    public void clearCache() {
        userDetailsCache.clear();
    }

    /**
     * 返回指定账号id所拥有的权限码集合
     *
     * @param loginId  账号id
     * @param loginType 账号类型
     * @return 该账号id具有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        UserDetails userDetails = loadUserDetails(loginId);
        if (userDetails != null && CollectionUtils.isNotEmpty(userDetails.getPermissionList())) {
            return userDetails.getPermissionList().stream()
                    .map(role -> role.getCode())
                    .toList();
        }
        return Collections.emptyList();
    }

    /**
     * 返回指定账号id所拥有的角色标识集合
     *
     * @param loginId  账号id
     * @param loginType 账号类型
     * @return 该账号id具有的角色标识集合
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        UserDetails userDetails = loadUserDetails(loginId);
        if (userDetails != null && CollectionUtils.isNotEmpty(userDetails.getRoleList())) {
            return userDetails.getRoleList().stream()
                    .map(role -> role.getCode())
                    .toList();
        }
        return Collections.emptyList();
    }
}
